﻿namespace Jhong.Mapper
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Reflection.Emit;
    using MapInfo = System.Collections.Generic.IDictionary<string, System.Reflection.PropertyInfo>;

    public static class Mapper<TFrom, TTo> where TTo : new()
    {
        private static MapInfo GetPropertiesInfo<T>()
        {
            var type = typeof(T);
            var mfType = typeof(MapPropertyAttribute);
            var ignore = typeof(MapIgnoreAttribute);
            var dic = new Dictionary<string, PropertyInfo>();
            foreach (var p in type.GetProperties())
            {
                if (p.GetGetMethod() == null || p.GetSetMethod() == null) continue;
                if (p.GetCustomAttribute(ignore, false) != null) continue;
                var attr = p.GetCustomAttribute(mfType, false);
                if (attr == null) dic.Add(p.Name, p);
                else dic.Add(((MapPropertyAttribute)attr).PropertyName, p);
            }
            return dic;
        }

        static Mapper()
        {
            var tFromPropertiesInfo = GetPropertiesInfo<TFrom>();
            var tToPropertiesInfo = GetPropertiesInfo<TTo>();
            _mapDelegate = BuildMapperDelegate(tFromPropertiesInfo, tToPropertiesInfo);
        }

        private static Func<TFrom, TTo> _mapDelegate;

        private static Func<TFrom, TTo> BuildMapperDelegate(MapInfo from, MapInfo to)
        {
            var dynamicMethod = new DynamicMethod(Guid.NewGuid().ToString("N"), typeof(TTo), new Type[] { typeof(TFrom) }, typeof(DataConvertor<TFrom, TTo>));
            var il = dynamicMethod.GetILGenerator();
            il.DeclareLocal(typeof(TTo));
            il.Emit(OpCodes.Newobj, typeof(TTo).GetConstructor(new Type[0]));
            il.Emit(OpCodes.Stloc_0);
            foreach (var t in to)
            {
                if (false == from.ContainsKey(t.Key)) continue;
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Callvirt, from[t.Key].GetGetMethod());
                if (from[t.Key].PropertyType.IsValueType)
                    il.Emit(OpCodes.Box, from[t.Key].PropertyType);
                il.Emit(OpCodes.Call, typeof(DataConvertor<,>).MakeGenericType(new Type[] { from[t.Key].PropertyType, t.Value.PropertyType }).GetMethod("Convert"));
                il.Emit(OpCodes.Callvirt, t.Value.GetSetMethod());
            }
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ret);
            return (Func<TFrom, TTo>)dynamicMethod.CreateDelegate(typeof(Func<TFrom, TTo>));
        }

        public static TTo AutoMap(TFrom from)
        {
            return _mapDelegate(from);
        }
    }
}